/**
 * futures : masterlink.futures.dao.HibernateBaseDao.java
 */
package masterlink.futures.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;

import baytony.util.Assert;
import baytony.util.Util;
import baytony.util.log.Log;

/**
 * TODO Write the description
 *
 * @author : tonyyang
 * Create date : 2011/10/25
 * @version 1.0.0	
 */
public abstract class HibernateDao<Bean extends Serializable, ID extends Serializable> implements Dao<Bean, ID> {
	
	private static final Object[] empty=new Object[0];
	protected Logger log=Log.getLogger(this.getClass());
	private Class<Bean> persistentBean;
	
	@SuppressWarnings("unchecked")
	public HibernateDao(){
		persistentBean=(Class<Bean>) ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}
	
	public abstract void setSessionFactory(SessionFactory factory);
	
	protected abstract Session getSession();
	
	public Object merge(Object obj){
		if(obj!=null){
			//log.trace("merge object {}",obj.toString());
			return getSession().merge(obj);
		}
		return null;
	}
	
	public void evict(Object obj){
		if(obj!=null){
			//log.trace("evict object {}",obj.toString());
			getSession().evict(obj);
		}
	}
	
	@Override
	public void save(Bean bean) {
		Assert.notNull(bean,"The given bean can not be null");
		long start=System.currentTimeMillis();
		try{
			log.trace("Begin save bean {} ",bean);
			getSession().save(bean);
		}catch (RuntimeException e) {
			log.error("Error happen while saving bean {}",bean,e);
			throw e;
		}finally{
			log.debug("Save bean {} execute time {} ms",bean,(System.currentTimeMillis()-start));
		}
	};
	
	@Override
	public void save(List<Bean> list) {
		if(!Util.isEmptyList(list)){
			for(Bean b:list){
				save(b);
			}
		}
	}
	
	@Override
	public void update(Bean bean) {
		Assert.notNull(bean,"The given bean can not be null");
		long start=System.currentTimeMillis();
		try{
			log.trace("Begin update bean {} ",bean);
			getSession().update(bean);
		}catch (RuntimeException e) {
			log.error("Error happen while updating bean {}",bean,e);
			throw e;
		}finally{
			log.debug("Update bean {} execute time {} ms",bean,(System.currentTimeMillis()-start));
		}
	};
	
	@Override
	public void update(List<Bean> list) {
		if(!Util.isEmptyList(list)){
			for(Bean b:list){
				update(b);
			}
		}
	}
	
	@Override
	public void delete(Bean bean) {
		Assert.notNull(bean,"The given bean can not be null");
		long start=System.currentTimeMillis();
		try{
			log.trace("Begin delete bean {} ",bean);
			getSession().delete(bean);
		}catch (RuntimeException e) {
			log.error("Error happen while deleting bean {}",bean,e);
			throw e;
		}finally{
			log.debug("Delete bean {} execute time {} ms",bean,(System.currentTimeMillis()-start));
		}
	};
	
	@Override
	public void delete(List<Bean> list) {
		if(!Util.isEmptyList(list)){
			for(Bean b:list){
				delete(b);
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public Bean findById(ID id) {
		Assert.notNull(id,"The id to find by can not be null");
		long start=System.currentTimeMillis();
		Bean b=null;
		try{
			log.trace("Finding bean {} by id={}",persistentBean.getName(),id);
			b=(Bean) getSession().get(persistentBean,id);
		}catch (RuntimeException e) {
			log.error("Error happen while find by id= {}",id,e);
			throw e;
		}finally{
			log.debug("{}. Find by id {} execute time {} ms",new Object[]{(b==null?"No object was found":b),id,(System.currentTimeMillis()-start)});
		}
		return b;
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<Bean> findAll() {
		long start=System.currentTimeMillis();
		List<Bean> all=null;
		try{
			log.trace("Find all Objects for bean {}",persistentBean.getName());
			all=getSession().createCriteria(persistentBean).list();
		}catch (RuntimeException e) {
			log.error("Error happen while find all bean {}",persistentBean.getName(),e);
			throw e;
		}finally{
			log.debug("{} Objects in Find all bean {} execute time {} ms",new Object[]{(all==null?0:all.size()),persistentBean.getName(),(System.currentTimeMillis()-start)});
		}
		return all;
	}
	
	protected List<?> findByHql(String hql,Object... params){
		return executeQuery(createHqlQuery(hql),params);
	}
	
	protected List<?> findBySql(String sql,Object... params){
		return executeQuery(createSqllQuery(sql),params);
	}
	
	@SuppressWarnings("unchecked")
	protected <T> List<T> findClassBySql(String sql,Class<T> clazz,Object... params){
		Assert.notNull(clazz, "The class entity to add can not be null for findBySql(String sql,Class<T> clazz,Object... params).");
		return (List<T>) executeQuery(createSqllQuery(sql).addEntity(clazz),params);
	}
	
	@SuppressWarnings("unchecked")
	protected List<Object[]> findClassBySql(String sql,Class<?>[] classes,Object... params){
		Assert.noNullElements(classes,"The classes to add can not be null or empty for findBySql(String sql,Class<?>[] classes,Object... params).");
		SQLQuery q=createSqllQuery(sql);
		for(Class<?> c:classes){
			q.addEntity(c);
		}
		return (List<Object[]>) executeQuery(q,params);
	}
	
	
	protected Query createHqlQuery(String hql) {
		Assert.hasText(hql,"The given hql can not be null");
		return getSession().createQuery(hql);
	}
	
	protected SQLQuery createSqllQuery(String sql) {
		Assert.hasText(sql,"The given sql can not be null");
		return getSession().createSQLQuery(sql);
	}
	
	protected Object[] checkParams(Object... params){
		if(params==null){
			return empty;
		}
		return params;
	}
	
	protected List<?> executeQuery(Query q,Object... params){
		Assert.notNull(q,"The query to execute can not be null");
		long start=System.currentTimeMillis();
		params=checkParams(params);
		List<?> all=null;
		try{
			log.trace("Execute query [{}] with params size {}",q.getQueryString(),params.length);
			for(int i=0;i<params.length;i++){
				log.trace("Parameter {}={}",i,params[i]);
				q.setParameter(i, params[i]);
			}
			all=q.list();
		}catch (RuntimeException e) {
			log.error("Error happen while execute query  [{}]. Exception {}",q.getQueryString(),e);
			throw e;
		}finally{
			log.debug("{} Object found for Query [{}] execute time {} ms",new Object[]{(all==null?0:all.size()),q.getQueryString(),(System.currentTimeMillis()-start)});
		}
		return all;
	}
	
	protected int executeUpateQuery(String sql,Object... params){
		return executeUpateQuery(createSqllQuery(sql),params);
	}
	
	protected int executeUpateQuery(Query q,Object... params){
		Assert.notNull(q,"The query to execute can not be null");
		long start=System.currentTimeMillis();
		params=checkParams(params);
		int result=0;
		try{
			log.trace("Execute query [{}] with params size {}",q.getQueryString(),params.length);
			for(int i=0;i<params.length;i++){
				log.trace("Parameter {}={}",i,params[i]);
				q.setParameter(i, params[i]);
			}
			result=q.executeUpdate();
			log.trace("{} data changes for query [{}]",result,q.getQueryString());
			return result;
		}catch (RuntimeException e) {
			log.error("Error happen while execute query [{}]",q.getQueryString(),e);
			throw e;
		}finally{
			log.debug("{} Data change for Query [{}] execute time {} ms",new Object[]{result,q.getQueryString(),(System.currentTimeMillis()-start)});
		}
	}
}
